/*
  FUSE: Filesystem in Userspace
  Copyright (C) 2001-2007  Miklos Szeredi <miklos@szeredi.hu>
  Copyright (C) 2011       Sebastian Pipping <sebastian@pipping.org>

  This program can be distributed under the terms of the GNU GPL.
  See the file COPYING.

  gcc -Wall fusexmp.c `pkg-config fuse --cflags --libs` -o fusexmp
*/

#define FUSE_USE_VERSION 26

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#ifdef linux
/* For pread()/pwrite()/utimensat() */
#define _XOPEN_SOURCE 700
#endif

#include <fuse.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <dirent.h>
#include <errno.h>
#include <sys/time.h>
#ifdef HAVE_SETXATTR
#include <sys/xattr.h>
#endif

#include "simplefs.h"
#include "lichbd.h"
#include "stor_root.h"

static int xmp_getattr(const char *_path, struct stat *stbuf)
{
        int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
	    ret = pfs_getattr(path, stbuf);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}

static int xmp_access(const char *_path, int mask)
{
        int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	    ret = pfs_access(path, mask);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}

static int xmp_readlink(const char *_path, char *buf, size_t size)
{
        int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	    ret = pfs_readlink(path, buf, size);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}


static int xmp_readdir(const char *_path, void *buf, fuse_fill_dir_t filler,
		       off_t offset, struct fuse_file_info *fi)
{
        (void) fi;

        int ret, done = 0;
        int delen;
        struct dirent *de0, *de;
		struct stat st;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

        de0 = NULL;
        while (done == 0) {
                ret = pfs_readdir(path, offset, (void **)&de0, &delen);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                if (delen == 0) {
                        done = 1;
                        break;
                }

                dir_for_each(de0, delen, de, offset) {
                        if (strlen(de->d_name) == 0) {
                                done = 1;
                                break;
                        }

                        memset(&st, 0, sizeof(st));
                        st.st_ino = de->d_ino;
                        st.st_mode = de->d_type << 12;
                        if (filler(buf, de->d_name, &st, 0))
                            break;

                }

                yfree((void **)&de0);
        }

        if (de0)
                yfree((void **)&de0);

        return 0;
err_ret:
        return -ret;
}

static int xmp_mknod(const char *_path, mode_t mode, dev_t rdev)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

    (void)rdev;
	ret = pfs_create(path, mode);
    if (unlikely(ret))
        GOTO(err_ret, ret);

	return 0;
err_ret:
    return -ret;
}

static int xmp_mkdir(const char *_path, mode_t mode)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_mkdir(path, mode);
    if (unlikely(ret))
        GOTO(err_ret, ret);

	return 0;
err_ret:
    return -ret;
}

static int xmp_unlink(const char *_path)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_unlink(path);
    if (unlikely(ret))
        GOTO(err_ret, ret);

    return 0;
err_ret:
	return -ret;
}

static int xmp_rmdir(const char *_path)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_rmdir(path);
    if (unlikely(ret))
        GOTO(err_ret, ret);

    return 0;
err_ret:
	return -ret;
}

static int xmp_symlink(const char *from, const char *to)
{
        int ret;

	    ret = pfs_symlink(from, to);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}

static int xmp_rename(const char *from, const char *to)
{
        int ret;

	    ret = pfs_rename(from, to);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}

static int xmp_link(const char *from, const char *to)
{
        int ret;

	    ret = pfs_link(from, to);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}

static int xmp_chmod(const char *_path, mode_t mode)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_chmod(path, mode);
    if (unlikely(ret))
        GOTO(err_ret, ret);

    return 0;
err_ret:
	return -ret;
}

static int xmp_chown(const char *_path, uid_t uid, gid_t gid)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_chown(path, uid, gid);
    if (unlikely(ret))
        GOTO(err_ret, ret);

    return 0;
err_ret:
	return -ret;
}

static int xmp_truncate(const char *_path, off_t size)
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_truncate(path, size);
    if (unlikely(ret))
        GOTO(err_ret, ret);

    return 0;
err_ret:
	return -ret;
}

static int xmp_utimens(const char *_path, const struct timespec ts[2])
{
	int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	ret = pfs_utimens(path, ts[0].tv_sec, ts[1].tv_sec, -1);
    if (unlikely(ret))
        GOTO(err_ret, ret);

    return 0;
err_ret:
	return -ret;
}

static int xmp_open(const char *_path, struct fuse_file_info *fi)
{
        int fd;
        int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	    fd = pfs_open(path, O_RDONLY, 0755);
        if (fd < 0) {
                ret = -fd;
                GOTO(err_ret, ret);
        }

        fi->fh = fd;

        pfs_close(fd);
        return 0;
err_ret:
        return -ret;
}

static int xmp_read(const char *_path, char *buf, size_t size, off_t offset,
		    struct fuse_file_info *fi)
{
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
#if 0
        (void) path;
        return pfs_pread(fi->fh, buf, size, offset);
#else
        (void) fi;
        return pfs_pread_with_path(path, buf, size, offset);
#endif
}

static int xmp_write(const char *_path, const char *buf, size_t size,
		     off_t offset, struct fuse_file_info *fi)
{
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
#if 0
        (void) path;
        return pfs_pwrite(fi->fh, buf, size, offset);
#else
        (void) fi;
        return pfs_pwrite_with_path(path, buf, size, offset);
#endif
}

static int xmp_statfs(const char *_path, struct statvfs *stbuf)
{
        int ret;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	    ret = pfs_statfs(path, stbuf);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return -ret;
}

static int xmp_release(const char *_path, struct fuse_file_info *fi)
{
	/* Just a stub.	 This method is optional and can safely be left
	   unimplemented */
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	(void) path;
	(void) fi;
	return 0;
}

static int xmp_fsync(const char *_path, int isdatasync,
		     struct fuse_file_info *fi)
{
	/* Just a stub.	 This method is optional and can safely be left
	   unimplemented */

        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
	(void) path;
	(void) isdatasync;
	(void) fi;
	return 0;
}

#ifdef HAVE_POSIX_FALLOCATE
static int xmp_fallocate(const char *_path, int mode,
			off_t offset, off_t length, struct fuse_file_info *fi)
{
	int fd;
	int res;
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);

	(void) fi;

	if (mode)
		return -EOPNOTSUPP;

	fd = open(path, O_WRONLY);
	if (fd == -1)
		return -errno;

	res = -posix_fallocate(fd, offset, length);

	close(fd);
	return res;
}
#endif

#ifdef HAVE_SETXATTR
/* xattr operations are optional and can safely be left unimplemented */
static int xmp_setxattr(const char *_path, const char *name, const char *value,
			size_t size, int flags)
{
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
	int res = lsetxattr(path, name, value, size, flags);
	if (res == -1)
		return -errno;
	return 0;
}

static int xmp_getxattr(const char *_path, const char *name, char *value,
			size_t size)
{
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
	int res = lgetxattr(path, name, value, size);
	if (res == -1)
		return -errno;
	return res;
}

static int xmp_listxattr(const char *_path, char *list, size_t size)
{
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
	int res = llistxattr(path, list, size);
	if (res == -1)
		return -errno;
	return res;
}

static int xmp_removexattr(const char *_path, const char *name)
{
        char path[MAX_PATH_LEN];

        sprintf(path, FUSE_ROOT"%s", _path);
	int res = lremovexattr(path, name);
	if (res == -1)
		return -errno;
	return 0;
}
#endif /* HAVE_SETXATTR */

static struct fuse_operations xmp_oper = {
	.getattr	= xmp_getattr,
	.access		= xmp_access,
	.readlink	= xmp_readlink,
	.readdir	= xmp_readdir,
	.mknod		= xmp_mknod,
	.mkdir		= xmp_mkdir,
	.symlink	= xmp_symlink,
	.unlink		= xmp_unlink,
	.rmdir		= xmp_rmdir,
	.rename		= xmp_rename,
	.link		= xmp_link,
	.chmod		= xmp_chmod,
	.chown		= xmp_chown,
	.truncate	= xmp_truncate,
	.utimens	= xmp_utimens,
	.open		= xmp_open,
	.read		= xmp_read,
	.write		= xmp_write,
	.statfs		= xmp_statfs,
	.release	= xmp_release,
	.fsync		= xmp_fsync,
#ifdef HAVE_POSIX_FALLOCATE
	.fallocate	= xmp_fallocate,
#endif
#ifdef HAVE_SETXATTR
	.setxattr	= xmp_setxattr,
	.getxattr	= xmp_getxattr,
	.listxattr	= xmp_listxattr,
	.removexattr	= xmp_removexattr,
#endif
};

int main(int argc, char *argv[])
{
    int ret;
    chkid_t rootid;

	umask(0);

#if 0
        ret = env_init_simple("lich.attr");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = network_connect_master();
        if (unlikely(ret)) {
            GOTO(err_ret, ret);
        }

        ret = stor_init(NULL, -1);
        if (unlikely(ret))
                GOTO(err_ret, ret);
#endif
        ret = lichbd_init("");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = stor_root("default", FUSE_ROOT, &rootid);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = pfs_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

	return fuse_main(argc, argv, &xmp_oper, NULL);

err_ret:
    return ret;
}
